package kr.co.bnsmedia.touchconfig;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

public class CalibrationView extends View {

	private static final int CALIBRATION_WIDTH = 1920;
	private static final int CALIBRATION_HEIGHT = 1080;

	private static final int CAMERA_LEFT = 0;
	private static final int CAMERA_RIGHT = 1;
	private static final int POINTDATA_MAX_NUM = 4;

	private static final int CALIBRATION_RADIAN_NUM = 50;
	private static final int CALIBRATION_MOVINGAVERAGE_NUM = 8;

	private MyApp mApp = null;

	private Rect mRect = new Rect();
	private Paint mPaintBackground = new Paint();
	private Paint mPaintLineBlack = new Paint();
	private Paint mPaintLineRed = new Paint();

	private List<Point> mScreenPts = new ArrayList<Point>();
	private Point mLeftOrigin = new Point();
	private Point mRightOrigin = new Point();
	private float[] mLeftCoeffs = null;
	private float[] mRightCoeffs = null;

	private boolean mIsStart = false;

	private PacketRadian[] mLeftRadians = new PacketRadian[g.CALIBRATION_RADIAN_MAX_NUM];
	private PacketRadian[] mRightRadians = new PacketRadian[g.CALIBRATION_RADIAN_MAX_NUM];
	private int[] mNumRadians = {0, 0, 0, 0};
	private byte mRecvDevice = 0;

	private int mMovingAverageCount = 0;
	private PointF[] mMovingAverageSamples = new PointF[CALIBRATION_MOVINGAVERAGE_NUM];
	private List<PointF> mRadianSamples = new ArrayList<PointF>();
	private int mScreenRadianIndex = 0;
	private PointF[] mScreenRadians = new PointF[g.DATA_COEFFS_NUM];

	private List<PointData> mPointDatas = new ArrayList<PointData>();

	public CalibrationView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		init();
	}

	public CalibrationView(Context context, AttributeSet attrs) {
		super(context, attrs);

		init();
	}

	public CalibrationView(Context context) {
		super(context);

		init();
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		mRect.set(0, 0, w, h);
		MyApp.log("onSizeChanged : " + w + ", " + h);

		int x = (int)(CALIBRATION_WIDTH * g.DATA_COEFFS_MARGIN_SX*0.01 + 0.5);
		int y = (int)(CALIBRATION_HEIGHT * g.DATA_COEFFS_MARGIN_SY*0.01 + 0.5);
		mScreenPts.add(new Point(x, y));

		x = (int)(CALIBRATION_WIDTH * 25*0.01 + 0.5);
		y = (int)(CALIBRATION_HEIGHT * g.DATA_COEFFS_MARGIN_SY*0.01 + 0.5);
		mScreenPts.add(new Point(x, y));

		x = (int)(CALIBRATION_WIDTH * 75*0.01 + 0.5);
		y = (int)(CALIBRATION_HEIGHT * g.DATA_COEFFS_MARGIN_SY*0.01 + 0.5);
		mScreenPts.add(new Point(x, y));

		x = (int)(CALIBRATION_WIDTH * g.DATA_COEFFS_MARGIN_EX*0.01 + 0.5);
		y = (int)(CALIBRATION_HEIGHT * g.DATA_COEFFS_MARGIN_SY*0.01 + 0.5);
		mScreenPts.add(new Point(x, y));

		x = (int)(CALIBRATION_WIDTH * g.DATA_COEFFS_MARGIN_EX*0.01 + 0.5);
		y = (int)(CALIBRATION_HEIGHT * g.DATA_COEFFS_MARGIN_EY*0.01 + 0.5);
		mScreenPts.add(new Point(x, y));

		x = (int)(CALIBRATION_WIDTH * 75*0.01 + 0.5);
		y = (int)(CALIBRATION_HEIGHT * g.DATA_COEFFS_MARGIN_EY*0.01 + 0.5);
		mScreenPts.add(new Point(x, y));

		x = (int)(CALIBRATION_WIDTH * 25*0.01 + 0.5);
		y = (int)(CALIBRATION_HEIGHT * g.DATA_COEFFS_MARGIN_EY*0.01 + 0.5);
		mScreenPts.add(new Point(x, y));

		x = (int)(CALIBRATION_WIDTH * g.DATA_COEFFS_MARGIN_SX*0.01 + 0.5);
		y = (int)(CALIBRATION_HEIGHT * g.DATA_COEFFS_MARGIN_EY*0.01 + 0.5);
		mScreenPts.add(new Point(x, y));

		for (int i=0; i<g.DATA_COEFFS_NUM; i++) {
			MyApp.log("" + i + " : " + mScreenPts.get(i).x + ", " + mScreenPts.get(i).y);
		}

		super.onSizeChanged(w, h, oldw, oldh);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		canvas.drawRect(mRect, mPaintBackground);

		if (mIsStart) {
			for (int i=0; i<g.DATA_COEFFS_NUM; i++) {
				if (mScreenRadianIndex == i) {
					canvas.drawLine(mScreenPts.get(i).x-20, mScreenPts.get(i).y, mScreenPts.get(i).x+20, mScreenPts.get(i).y, mPaintLineRed);
					canvas.drawLine(mScreenPts.get(i).x, mScreenPts.get(i).y-20, mScreenPts.get(i).x, mScreenPts.get(i).y+20, mPaintLineRed);
				} else {
					canvas.drawLine(mScreenPts.get(i).x-20, mScreenPts.get(i).y, mScreenPts.get(i).x+20, mScreenPts.get(i).y, mPaintLineBlack);
					canvas.drawLine(mScreenPts.get(i).x, mScreenPts.get(i).y-20, mScreenPts.get(i).x, mScreenPts.get(i).y+20, mPaintLineBlack);
				}
			}
		} else {
			for (int i=0; i<mPointDatas.size(); i++) {
				if (mPointDatas.get(i).isValid) {
					int x = (int)(mPointDatas.get(i).fPt.x * CALIBRATION_WIDTH + 0.5);
					int y = (int)(mPointDatas.get(i).fPt.y * CALIBRATION_HEIGHT + 0.5);
					canvas.drawLine(mLeftOrigin.x, mLeftOrigin.y, x, y, mPaintLineBlack);
					canvas.drawLine(x, y, mRightOrigin.x, mRightOrigin.y, mPaintLineBlack);
				}
			}
		}
	}

	public void setMyApp(MyApp myApp) {
		mApp = myApp;

		mLeftCoeffs = mApp.cameraConfig(0).fCoeffs;
		mRightCoeffs = mApp.cameraConfig(1).fCoeffs;
		for (int i=0; i<g.DATA_COEFFS_NUM; i++) {
			MyApp.log("" + i + " : " + mLeftCoeffs[i] + ", " + mRightCoeffs[i]);
		}
		updateOrigin();
	}

	public void setStart(boolean start) {
		mIsStart = start;
		mScreenRadianIndex = 0;
		invalidate();
	}

	public void setData(int aDev, int aDlen, byte[] aData) {
		int idx = aDev - g.device.CAMERA1;
		int count = aDlen / PacketData.RADIAN_LEN;
		if ((g.device.MAX_CAMERA <= idx) || (g.CALIBRATION_RADIAN_MAX_NUM < count)) {
			return;
		}

		for (int i=0; i<count; i++) {
			if (0 == idx) {
				mLeftRadians[i].set(i*PacketData.RADIAN_LEN, aData);
			} else {
				mRightRadians[i].set(i*PacketData.RADIAN_LEN, aData);
			}
		}
		mNumRadians[idx] = count;
		mRecvDevice |= (1<<idx);
		if (((1<<CAMERA_RIGHT) | (1<<CAMERA_LEFT)) != mRecvDevice) {
			return;
		}
		mRecvDevice = 0;

		if (mIsStart) {
			if (g.DATA_COEFFS_NUM <= mScreenRadianIndex) {
				return;
			}

			int numPoint = mNumRadians[CAMERA_LEFT] * mNumRadians[CAMERA_RIGHT];
			if (1 != numPoint) {
				return;
			}

			PointF radianData = new PointF(0, 0);
			int nIdx = mMovingAverageCount++ % CALIBRATION_MOVINGAVERAGE_NUM;

			mMovingAverageSamples[nIdx].set(mLeftRadians[0].fCenter, mRightRadians[0].fCenter);
			nIdx = Math.min(mMovingAverageCount, CALIBRATION_MOVINGAVERAGE_NUM);
			for (int i=0; i<nIdx; i++) {
				radianData.x += mMovingAverageSamples[i].x;
				radianData.y += mMovingAverageSamples[i].y;
			}
			radianData.x /= nIdx;
			radianData.y /= nIdx;

			if (CALIBRATION_RADIAN_NUM > mRadianSamples.size()) {
				mRadianSamples.add(radianData);
				return;
			}
			mRadianSamples.remove(0);
			mRadianSamples.add(radianData);
			for (int i=1; i<mRadianSamples.size(); i++) {
				float fOffsetX = Math.abs(mRadianSamples.get(0).x - mRadianSamples.get(i).x);
				float fOffsetY = Math.abs(mRadianSamples.get(0).y - mRadianSamples.get(i).y);
				if ((0.0003 < fOffsetX) || (0.0003 < fOffsetY)) {
					//MyApp.log("ERR offset : " + fOffsetX + ", " + fOffsetY);
					return;
				}
			}

			PointF radianTotalData = new PointF(0, 0);
			for (int i=0; i<CALIBRATION_RADIAN_NUM; i++) {
				radianTotalData.x += mRadianSamples.get(i).x;
				radianTotalData.y += mRadianSamples.get(i).y;
			}
			mScreenRadians[mScreenRadianIndex].x = radianTotalData.x / (float)CALIBRATION_RADIAN_NUM;
			mScreenRadians[mScreenRadianIndex].y = radianTotalData.y / (float)CALIBRATION_RADIAN_NUM;
			MyApp.log("" + mScreenRadianIndex + " - " + mScreenRadians[mScreenRadianIndex].x + ", " + mScreenRadians[mScreenRadianIndex].y);
			{
				byte[] b10msec = {15};
				mApp.wirtePacket(g.command.SET_BUZZER, g.device.TOUCH, b10msec.length, b10msec);
			}

			mMovingAverageCount = 0;
			mRadianSamples.clear();
			mScreenRadianIndex++;
			if (g.DATA_COEFFS_NUM == mScreenRadianIndex) {
				float fCoeffs = 0;
				byte[] bCoeffs1 = new byte[g.DATA_COEFFS_NUM*4];
				byte[] bCoeffs2 = new byte[g.DATA_COEFFS_NUM*4];

				for (int i=0; i<g.DATA_COEFFS_NUM; i++) {
					fCoeffs = (float)Math.tan(mScreenRadians[i].x);
					MyApp.log("Left(" + i + ") - " + fCoeffs);
					System.arraycopy(PacketData.floatToByteArray(fCoeffs), 0, bCoeffs1, i*4, 4);
				}
				mApp.wirtePacket(g.command.SET_COEFFS, g.device.CAMERA1, bCoeffs1.length, bCoeffs1);

				for (int i=0; i<g.DATA_COEFFS_NUM; i++) {
					fCoeffs = (float)Math.tan(mScreenRadians[i].y);
					MyApp.log("Right(" + i + ") - " + fCoeffs);
					System.arraycopy(PacketData.floatToByteArray(fCoeffs), 0, bCoeffs2, i*4, 4);
				}
				mApp.wirtePacket(g.command.SET_COEFFS, g.device.CAMERA2, bCoeffs2.length, bCoeffs2);
			}

			invalidate();
		} else {
			mPointDatas.clear();
			for (int l=0; l<mNumRadians[CAMERA_LEFT]; l++) {
				for (int r=0; r<mNumRadians[CAMERA_RIGHT]; r++) {
					if (POINTDATA_MAX_NUM <= mPointDatas.size()) {
						break;
					}
					PointData pointData = new PointData();
					radian_pointdata(mLeftRadians[l], mRightRadians[r], pointData);
					mPointDatas.add(pointData);
				}
			}

			invalidate();
		}
	}


	private void init() {
		mPaintBackground.setColor(Color.WHITE);
		mPaintLineBlack.setColor(Color.BLACK);
		mPaintLineRed.setColor(Color.RED);
		mPaintLineRed.setStrokeWidth(2);

		for (int i=0; i<g.CALIBRATION_RADIAN_MAX_NUM; i++) {
			mLeftRadians[i] = new PacketRadian();
			mRightRadians[i] = new PacketRadian();
		}
		for (int i=0; i<CALIBRATION_MOVINGAVERAGE_NUM; i++) {
			mMovingAverageSamples[i] = new PointF();
		}
		for (int i=0; i<g.DATA_COEFFS_NUM; i++) {
			mScreenRadians[i] = new PointF();
		}
	}

	private class PointData {
		public PointF fPt;
		public boolean isValid;

		public PointData() {
			fPt = new PointF();
			isValid = false;
		}
	}


	private static final float[] X_LINES = {
		(float)(g.DATA_COEFFS_MARGIN_SX*0.01),
		(float)(25*0.01),
		(float)(75*0.01),
		(float)(g.DATA_COEFFS_MARGIN_EX*0.01)
	};
	private static final float[] Y_LINES = {
		(float)(g.DATA_COEFFS_MARGIN_SY*0.01),
		(float)(g.DATA_COEFFS_MARGIN_EY*0.01)
	};
	private void point_x_position(float tan1, float tan2, float tan3, float x1, float x3, float y, PointF fPt) {
		float fX = x1 + (x3 - x1) * (tan3 * (tan1 - tan2)) / (tan2 * (tan1 - tan3));
		float fY = y;
		fPt.set(fX, fY);
	}
	private void point_y_position(float tan1, float tan2, float tan3, float y1, float y3, float x, PointF fPt) {
		float fX = x;
		float fY = y1 + (y3 - y1) * (tan2 - tan1) / (tan3 - tan1);
		fPt.set(fX, fY);
	}
	private boolean point_intersection(PointF p1, PointF p2, PointF p3, PointF p4, PointF fPt) {
		float d = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x);
		if (0.0 != d) {
			float pre = (p1.x*p2.y - p1.y*p2.x);
			float post = (p3.x*p4.y - p3.y*p4.x);
			float fX = (pre * (p3.x - p4.x) - (p1.x - p2.x) * post) / d;
			float fY = (pre * (p3.y - p4.y) - (p1.y - p2.y) * post) / d;

			fPt.set(fX, fY);
			if ((0<=fX) && (1>=fX) && (0<=fY) && (1>=fY))
				return true;
		}
		return false;
	}
	private void radian_pointdata(PacketRadian leftRadian, PacketRadian rightRadian, PointData pointData) {
		float fCoeff;
		PointF fLeftPt0 = new PointF();
		PointF fLeftPt1 = new PointF();
		PointF fRightPt0 = new PointF();
		PointF fRightPt1 = new PointF();

		fCoeff = (float)Math.tan(leftRadian.fCenter);
		if (mLeftCoeffs[2] > fCoeff) {
			point_x_position(mLeftCoeffs[2], fCoeff, mLeftCoeffs[3], X_LINES[2], X_LINES[3], Y_LINES[0], fLeftPt0);
			point_y_position(mLeftCoeffs[3], fCoeff, mLeftCoeffs[4], Y_LINES[0], Y_LINES[1], X_LINES[3], fLeftPt1);
		} else if (mLeftCoeffs[1] > fCoeff) {
			point_x_position(mLeftCoeffs[1], fCoeff, mLeftCoeffs[2], X_LINES[1], X_LINES[2], Y_LINES[0], fLeftPt0);
			point_y_position(mLeftCoeffs[2], fCoeff, mLeftCoeffs[5], Y_LINES[0], Y_LINES[1], X_LINES[2], fLeftPt1);
		} else if (mLeftCoeffs[5] > fCoeff) {
			point_y_position(mLeftCoeffs[1], fCoeff, mLeftCoeffs[6], Y_LINES[0], Y_LINES[1], X_LINES[1], fLeftPt0);
			point_y_position(mLeftCoeffs[2], fCoeff, mLeftCoeffs[5], Y_LINES[0], Y_LINES[1], X_LINES[2], fLeftPt1);
		} else if (mLeftCoeffs[6] > fCoeff) {
			point_y_position(mLeftCoeffs[1], fCoeff, mLeftCoeffs[6], Y_LINES[0], Y_LINES[1], X_LINES[1], fLeftPt0);
			point_x_position(mLeftCoeffs[6], fCoeff, mLeftCoeffs[5], X_LINES[1], X_LINES[2], Y_LINES[1], fLeftPt1);
		} else {
			point_y_position(mLeftCoeffs[0], fCoeff, mLeftCoeffs[7], Y_LINES[0], Y_LINES[1], X_LINES[0], fLeftPt0);
			point_x_position(mLeftCoeffs[7], fCoeff, mLeftCoeffs[6], X_LINES[0], X_LINES[1], Y_LINES[1], fLeftPt1);
		}

		fCoeff = (float)Math.tan(rightRadian.fCenter);
		if (mRightCoeffs[1] > fCoeff) {
			point_x_position(mRightCoeffs[0], fCoeff, mRightCoeffs[1], X_LINES[0], X_LINES[1], Y_LINES[0], fRightPt0);
			point_y_position(mRightCoeffs[0], fCoeff, mRightCoeffs[7], Y_LINES[0], Y_LINES[1], X_LINES[0], fRightPt1);
		} else if (mRightCoeffs[2] > fCoeff) {
			point_x_position(mRightCoeffs[1], fCoeff, mRightCoeffs[2], X_LINES[1], X_LINES[2], Y_LINES[0], fRightPt0);
			point_y_position(mRightCoeffs[1], fCoeff, mRightCoeffs[6], Y_LINES[0], Y_LINES[1], X_LINES[1], fRightPt1);
		} else if (mRightCoeffs[6] > fCoeff) {
			point_y_position(mRightCoeffs[2], fCoeff, mRightCoeffs[5], Y_LINES[0], Y_LINES[1], X_LINES[2], fRightPt0);
			point_y_position(mRightCoeffs[1], fCoeff, mRightCoeffs[6], Y_LINES[0], Y_LINES[1], X_LINES[1], fRightPt1);
		} else if (mRightCoeffs[5] > fCoeff) {
			point_x_position(mRightCoeffs[6], fCoeff, mRightCoeffs[5], X_LINES[1], X_LINES[2], Y_LINES[1], fRightPt0);
			point_y_position(mRightCoeffs[2], fCoeff, mRightCoeffs[5], Y_LINES[0], Y_LINES[1], X_LINES[2], fRightPt1);
		} else {
			point_x_position(mRightCoeffs[5], fCoeff, mRightCoeffs[4], X_LINES[2], X_LINES[3], Y_LINES[1], fRightPt0);
			point_y_position(mRightCoeffs[3], fCoeff, mRightCoeffs[4], Y_LINES[0], Y_LINES[1], X_LINES[3], fRightPt1);
		}

		//		MyApp.log("(" + rightRadian.fCenter + " : " + leftRadian.fCenter + ")");
		pointData.isValid = point_intersection(fLeftPt0, fLeftPt1, fRightPt0, fRightPt1, pointData.fPt);
	}
	private void updateOrigin() {
		PointF fPt1 = new PointF();
		PointF fPt2 = new PointF();
		PointF fPt3 = new PointF();
		PointF fPt4 = new PointF();
		PointF fOrg = new PointF();

		point_y_position(mLeftCoeffs[1], mLeftCoeffs[4], mLeftCoeffs[6], Y_LINES[0], Y_LINES[1], X_LINES[1], fPt1);
		point_y_position(mLeftCoeffs[2], mLeftCoeffs[4], mLeftCoeffs[5], Y_LINES[0], Y_LINES[1], X_LINES[2], fPt2);
		point_y_position(mLeftCoeffs[1], mLeftCoeffs[5], mLeftCoeffs[6], Y_LINES[0], Y_LINES[1], X_LINES[1], fPt3);
		point_x_position(mLeftCoeffs[6], mLeftCoeffs[5], mLeftCoeffs[5], X_LINES[1], X_LINES[2], Y_LINES[1], fPt4);
		point_intersection(fPt1, fPt2, fPt3, fPt4, fOrg);
		mLeftOrigin.x = (int)(fOrg.x * CALIBRATION_WIDTH + 0.5);
		mLeftOrigin.y = (int)(fOrg.y * CALIBRATION_HEIGHT + 0.5);

		point_y_position(mRightCoeffs[2], mRightCoeffs[7], mRightCoeffs[5], Y_LINES[0], Y_LINES[1], X_LINES[2], fPt1);
		point_y_position(mRightCoeffs[1], mRightCoeffs[7], mRightCoeffs[6], Y_LINES[0], Y_LINES[1], X_LINES[1], fPt2);
		point_x_position(mRightCoeffs[6], mRightCoeffs[6], mRightCoeffs[5], X_LINES[1], X_LINES[2], Y_LINES[1], fPt3);
		point_y_position(mRightCoeffs[2], mRightCoeffs[6], mRightCoeffs[5], Y_LINES[0], Y_LINES[1], X_LINES[2], fPt4);
		point_intersection(fPt1, fPt2, fPt3, fPt4, fOrg);
		mRightOrigin.x = (int)(fOrg.x * CALIBRATION_WIDTH + 0.5);
		mRightOrigin.y = (int)(fOrg.y * CALIBRATION_HEIGHT + 0.5);
	}
}
